/*******************************************************************************
Description:  Defines the main application entry point.
Author:       Ghassan Shobaki
Created:      Unknown.
Last Update:  Mar. 2011
*******************************************************************************/

// For struct FILE.
#include <cstdio>
// For class fstream.
#include <fstream>
// For class string.
#include <string>
// For class list<>.
#include <list>
// For class set<>.
#include <set>
#include "generic/defines.h"
#include "generic/random.h"
#include "generic/logger.h"
#include "generic/utilities.h"
#include "generic/buffers.h"
#include "generic/stats.h"
#include "generic/config.h"
#include "basic/sched_basic_data.h"
#include "basic/machine_model.h"
#include "basic/data_dep.h"
#include "enum/enumerator.h"
#include "spill/bb_spill.h"

using namespace opt_sched;
using std::string;

// TODO(max): Refactor to bits.

/*******************************************************************************
File-level variables.
*******************************************************************************/

// Files

// The output DAG file stream.
static FILE* outFile_ = NULL;
// The filepath of the configuration file.
static string setupFullPath_ = "../input/sched.ini";

// Workhorse objects.

// The configuration manager.
static Config config;
// A pointer to the currently loaded machine model object.
static MachineModel* machMdl_ = NULL;
// A pointer to the current dependence graph.
static DataDepGraph* dataDepGraph_ = NULL;

// The program return value. Never read. Set to non-zero when errors occur.
static int retVal_ = 1;

// Settings.

// A set of numbers of blocks which should be skipped.
static std::set<int64_t> skipSet_;
// The number of the first block in a range to skip.
static int64_t skipMin_ = INVALID_VALUE;
// The number of the last block in a range to skip.
static int64_t skipMax_ = INVALID_VALUE;

// State.

// The (serial) number of the current block.
static int64_t crntBlockNum_ = 0;

// Statistics.

// The number of fully scheduled block.
static int64_t schduldBlockCnt_ = 0;
// The number of list scheduled block.
static int64_t lstSchduldBlockCnt_ = 0;
// The number of "easy" (i.e. not hard) blocks.
static int64_t easyBlockCnt_ = 0;
// The number of blocks that timed out before scheduling was finished.
static int64_t timedOutBlockCnt_ = 0;
// The number of blocks which have been improved and became optimal.
static int64_t optmlImprvdBlockCnt_ = 0;
// The number of blocks which have been improved but did not become optimal.
static int64_t nonOptmlImprvdBlockCnt_ = 0;

// The number of blocks with unpipelined instructions.
static int64_t unpipelinedBlockCnt_ = 0;
// The number of blocks with unsupported instructions.
static int64_t unsupportedBlockCnt_ = 0;
// The number of non-standard blocks.
static int64_t nonStandardBlockCnt_ = 0;
// The number of invalid blocks.
static int64_t invldBlockCnt_ = 0;

// The total number of cycles eliminated by switching from list scheduling
// to optimal scheduling.
static int64_t cycleImprvmnt_ = 0;
// The total number of cycles resulting from list scheduling.
static int64_t totLstSchedLngth_ = 0;
// The total number of cycles in hard blocks.
static int64_t totHardSchedLngth_ = 0;

// The total number of cycles eliminated by switching from list scheduling
// to optimal scheduling, weighted by block execution frequency.
static float wghtdCycleImprvmnt_ = 0;
// The total number of cycles resulting from list scheduling, weighted by
// block execution frequency.
static float wghtdTotLstSchedLngth_ = 0;
// The total number of cycles in hard blocks, weighted by block execution
// frequency.
static float wghtdTotHardSchedLngth_ = 0;

// The total cost eliminated by switching from list scheduling to optimal
// scheduling, weighted by block execution frequency.
static float wghtdCostImprvmnt_ = 0;
// The total cost of the list schedule, weighted by block execution
// frequency.
static float wghtdTotHurstcCost_ = 0;
// The total cost of the hard blocks when using list scheduling, weighted by
// block execution frequency.
static float wghtdTotHardCost_ = 0;

// The maximum single improvement produced by the optimization.
static int64_t maxImprvmnt_ = 0;
// The ID of the block with the maximum improvement.
static string maxImprvdDagId_ = "NONE";
// The maximum single weighted improvement produced by the optimization.
static float maxWghtdImprvmnt_ = 0;
// The ID of the block with the maximum weighted improvement.
static string maxWghtImprvdDagId_ = "NONE";

/*******************************************************************************
Function signatures.
*******************************************************************************/

// Parsers for enums.
LISTSCHED_HEURISTIC ParseHeuristic(const string& name);
LB_ALG ParseLBAlg(const string& name);
OUTPUT_DAGS ParseOutptDagsFilter(const string& name);

// Completely sets up the controller. Loads the settings and machine model
// and reads the input file.
void Setup();

// Deallocates previously allocated memory.
void Destroy();

// Prints out some statistical summary.
void PrintSummary();
// Prints out final statistical summary.
void PrintFinalSummary();

// Creates the internal dependence graph with previously read settings and
// returns a pointer to it. The graph still does not contain data. The user
// should delete the graph when it is no longer used.
DataDepGraph* CreateDataDepGraph();
// Processes a given graph and returns a pointer to the optimized schedule.
void ProcessBlock(DataDepGraph* dataDepGraph);

// Records the fact that a specified graph has been improved by a given
// cost value and a given number of cycles.
void RcrdImprvmnt(int costImprvmnt,
                  int cycleImprvmnt,
                  DataDepGraph* depGraph,
                  bool isOptimal);

// Initializes settings by reading the config file (setupFullPath_).
void ReadSettings_();
// Returns whether the block with the given number is in the skip list.
bool ShouldBeSkipped(int64_t blockNum);
// Compare calculated results to original input in the specified graph,
// given the result of SchedRegion::FindOptimalSchedule().
void CmprRslts_(DataDepGraph* dataDepGraph, FUNC_RESULT rslt);

// Schedules the specified region.
FUNC_RESULT ProcessRegion_(SchedRegion* rgn);
// Processes a block using a cost function that accounts for spill cost.
void ProcessBasicBlockWithSpillCost_(DataDepGraph* dataDepGraph);

// The main entry point. Takes the following command line arguments:
// input_filename machine_name timeout_period [maximum_blocks]
int main() {
  SpecsBuffer specsBuf;
  DataDepGraph* dataDepGraph;
  FUNC_RESULT readRslt;
  int64_t maxBlocks;
  bool endOfFileReached = false;

  Setup();

  maxBlocks = config.GetInt("MAX_BLOCKS_TO_PROCESS", INVALID_VALUE);

  string inpFullPath = config.GetString("INPUT_FILE");
  if (specsBuf.Load(inpFullPath.c_str()) != RES_SUCCESS) {
    Logger::Fatal("File not found: %s.", inpFullPath.c_str());
  }

  while (!endOfFileReached && crntBlockNum_ != maxBlocks) {
    dataDepGraph = CreateDataDepGraph();
    readRslt = dataDepGraph->ReadFrmFile(&specsBuf, endOfFileReached);

    if (readRslt == RES_SUCCESS) {
      ProcessBlock(dataDepGraph);
    } else {
      // Record the fact that an invalid block has been passed.
      Logger::Info("Recovering from parsing error...");
      invldBlockCnt_++;
      crntBlockNum_++;
      retVal_ = 1;
      readRslt = dataDepGraph->SkipGraph(&specsBuf, endOfFileReached);
    }

    delete dataDepGraph;
  }

  PrintSummary();
  PrintFinalSummary();

  Destroy();

  return retVal_;
}

inline double clcltPercentage(double numerator, double denominator) {
  if (denominator == 0) return 0;
  return numerator * 100 / denominator;
}

inline double clcltPercentage(int64_t numerator, int64_t denominator) {
  return clcltPercentage((double)numerator, (double)denominator);
}

void Destroy() {
  if (outFile_ != NULL) fclose(outFile_);
  if (machMdl_ != NULL) delete machMdl_;
}

void Setup() {
  ReadSettings_();

  Logger::RegisterPeriodicLogger(config.GetInt("LOG_PERIOD"), PrintSummary);

  RandomGen::SetSeed((int32_t)config.GetInt("RANDOM_SEED"));

  machMdl_ = new MachineModel(config.GetString("MACHINE_MODEL_FILE"));
  if (machMdl_ == NULL) Logger::Fatal("Out of memory.");

  string outFileName = config.GetString("OUTPUT_FILE");
  if (outFileName != "NONE") {
    if (outFileName == "AUTO") {
      outFileName = config.GetString("INPUT_FILE") + ".out";
    }
    outFile_ = fopen(outFileName.c_str(), "w");
    if (outFile_ == NULL) {
      Logger::Error("Can't open output file %s.", outFileName.c_str());
    }
  }
}

void ReadSettings_() {
  config.Load(setupFullPath_);

  // Filters
  std::list<int64_t> range = config.GetIntList("SKIP_RANGE");

  if (range.size()) {
    std::list<int64_t>::iterator it = range.begin();
    skipMin_ = *it;
    skipMax_ = *++it;
  } else {
    skipMin_ = skipMax_ = INVALID_VALUE;
  }

  std::list<int64_t> skipList = config.GetIntList("SKIP_LIST");
  skipSet_ = std::set<int64_t>(skipList.begin(), skipList.end());
}

DataDepGraph* CreateDataDepGraph() {
  string outputDagFiltersString = config.GetString("OUTPUT_DAGS");
  OUTPUT_DAGS outputDagFilters = ParseOutptDagsFilter(outputDagFiltersString);

  dataDepGraph_ = new DataDepGraph(machMdl_,
                                   config.GetBool("USE_FILE_LATENCIES", true),
                                   outputDagFilters,
                                   (InstCount)config.GetInt("MAX_OUTPUT_DAG_SIZE"));
  if (dataDepGraph_ == NULL) Logger::Fatal("Out of memory.");
  return dataDepGraph_;
}

void PrintSummary() {
  if (crntBlockNum_ == 0) {
    Logger::Summary("No blocks were processed");
    return;
  }

  int64_t hardBlockCnt = schduldBlockCnt_ - easyBlockCnt_;
  int64_t optimalBlockCnt = hardBlockCnt - timedOutBlockCnt_;
  int64_t vldBlockCnt = crntBlockNum_ - invldBlockCnt_;

  double vldPrcntg = clcltPercentage(vldBlockCnt, crntBlockNum_);
  double lstSchduldPrcntg = clcltPercentage(lstSchduldBlockCnt_, crntBlockNum_);
  double schduldPrcntg = clcltPercentage(schduldBlockCnt_, crntBlockNum_);

  double unpipelinedPrcntg = clcltPercentage(unpipelinedBlockCnt_, vldBlockCnt);
  double unsupportedPrcntg = clcltPercentage(unsupportedBlockCnt_, vldBlockCnt);
  double nonStandardPrcntg = clcltPercentage(nonStandardBlockCnt_, vldBlockCnt);

  double hardPrcntg = clcltPercentage(hardBlockCnt, schduldBlockCnt_);
  double timeoutPrcntg = clcltPercentage(timedOutBlockCnt_, hardBlockCnt);
  double optmlPrcntg = clcltPercentage(optimalBlockCnt, hardBlockCnt);
  double optmlImprvdPrcntg = clcltPercentage(optmlImprvdBlockCnt_, hardBlockCnt);
  double nonOptmlImprvdPrcntg = clcltPercentage(nonOptmlImprvdBlockCnt_, hardBlockCnt);
  double imprvdCyclePrcntg = clcltPercentage((double)cycleImprvmnt_, (double)totHardSchedLngth_);
  double wghtdImprvdCyclePrcntg = clcltPercentage(wghtdCycleImprvmnt_, wghtdTotHardSchedLngth_);
  double wghtdImprvdCostPrcntg = clcltPercentage(wghtdCostImprvmnt_, wghtdTotHardCost_);
  double overallImprvdCyclePrcntg = clcltPercentage((double)cycleImprvmnt_, (double)totLstSchedLngth_);
  double overallWghtdImprvdCyclePrcntg = clcltPercentage(wghtdCycleImprvmnt_, wghtdTotLstSchedLngth_);
  double overallWghtdImprvdCostPrcntg = clcltPercentage(wghtdCostImprvmnt_, wghtdTotHurstcCost_);

  Logger::Info("-------------------------------------------------------------");
  Logger::Summary("Total DAGs: %lld",
                  crntBlockNum_);
  Logger::Summary("Total time: %.2lf sec",
                  Utilities::GetProcessorTime() / 1000.0f);
  Logger::Summary("Valid DAGs: %lld (%.2lf%%)",
                  vldBlockCnt, vldPrcntg);
  Logger::Summary("List scheduled DAGs: %lld (%.2lf%%)",
                  lstSchduldBlockCnt_, lstSchduldPrcntg);
  Logger::Summary("Fully scheduled DAGs: %lld (%.2lf%%)",
                  schduldBlockCnt_, schduldPrcntg);
  Logger::Summary("Hard DAGs: %lld (%.2lf%%)",
                  hardBlockCnt, hardPrcntg);
  Logger::Summary("Hard DAGs solved optimally: %lld (%.2lf%%)",
                  optimalBlockCnt, optmlPrcntg);
  Logger::Summary("DAGs timed out: %lld (%.2lf%%)",
                  timedOutBlockCnt_, timeoutPrcntg);
  Logger::Summary("DAGs improved and optimal: %lld (%.2lf%%)",
                  optmlImprvdBlockCnt_, optmlImprvdPrcntg);
  Logger::Summary("DAGs improved and non-optimal: %lld (%.2lf%%)",
                  nonOptmlImprvdBlockCnt_, nonOptmlImprvdPrcntg);
  Logger::Summary("");
  Logger::Summary("Cycles improved: %lld (hard: %.4lf%%) (overall: %.4lf%%)",
                  cycleImprvmnt_, imprvdCyclePrcntg, overallImprvdCyclePrcntg);
  Logger::Summary("Total heuristic schedule length: %lld",
                  totLstSchedLngth_);
  Logger::Summary("Total hard schedule length: %lld",
                  totHardSchedLngth_);
  Logger::Summary("Weighted cycles improved: %.2f (hard: %.4lf%%, overall: %.4lf%%)",
                  wghtdCycleImprvmnt_, wghtdImprvdCyclePrcntg, overallWghtdImprvdCyclePrcntg);
  Logger::Summary("Weighted total heuristic schedule length: %.2lf",
                  wghtdTotLstSchedLngth_);
  Logger::Summary("Weighted total hard schedule length: %.2lf",
                  wghtdTotHardSchedLngth_);
  Logger::Summary("Weighted cost improved: %.2f (hard: %.4lf%%, overall: %.4lf%%)",
                  wghtdCostImprvmnt_, wghtdImprvdCostPrcntg, overallWghtdImprvdCostPrcntg);
  Logger::Summary("Weighted total heuristic cost: %.2lf",
                  wghtdTotHurstcCost_);
  Logger::Summary("Weighted total hard cost: %.2lf",
                  wghtdTotHardCost_);
  Logger::Summary("Max cycle improvement: %lld for DAG %s",
                  maxImprvmnt_, maxImprvdDagId_.c_str());
  Logger::Summary("Max weighted cycle improvement: %.2lf for DAG %s",
                  maxWghtdImprvmnt_, maxWghtImprvdDagId_.c_str());
  Logger::Summary("");
  Logger::Summary("DAGs with unpipelined instructions: %lld (%.2lf%%)",
                  unpipelinedBlockCnt_, unpipelinedPrcntg);
  Logger::Summary("DAGs with unsupported instructions: %lld (%.2lf%%)",
                  unsupportedBlockCnt_, unsupportedPrcntg);
  Logger::Summary("DAGs with non-standard basic blocks: %lld (%.2lf%%)",
                  nonStandardBlockCnt_, nonStandardPrcntg);

  ostream& log = Logger::GetLogStream();

  log << Stats::scheduledLatency;

  if (config.GetBool("VERIFY_SCHEDULE", false)) log << Stats::invalidSchedules;

  log << "--------------------------------------------------------\n";
}

void PrintFinalSummary() {
  // Print out the settings used in the run to the summary log.
  Logger::Summary("Settings:");
  Logger::Summary("---------");
  Logger::Summary("Input filename: %s",
                  config.GetString("INPUT_FILE").c_str());
  Logger::Summary("Machine model: %s",
                  machMdl_->GetModelName().c_str());
  Logger::Summary("Timeout period (ms): %s",
                  config.GetString("TIMEOUT", "No limit").c_str());
  Logger::Summary("Region type: BB_SPILL_COST");
  Logger::Summary("List scheduling heuristic: %s",
                  config.GetString("HEURISTIC").c_str());
  Logger::Summary("Enumerator heuristic: %s",
                  config.GetString("ENUM_HEURISTIC").c_str());
  Logger::Summary("Lower-bound algorithm: %s",
                  config.GetString("LB_ALG", "LC").c_str());
  Logger::Summary("Apply relaxed pruning: %s",
                  config.GetString("APPLY_RELAXED_PRUNING", "YES").c_str());
  Logger::Summary("Apply node superiority: %s",
                  config.GetString("APPLY_NODE_SUPERIORITY", "YES").c_str());
  Logger::Summary("Apply history domination: %s",
                  config.GetString("APPLY_HISTORY_DOMINATION", "YES").c_str());

  // Print out some statistics.
  ostream& log = Logger::GetLogStream();

  log << "--------------------------------------------------------\n";

  if (config.GetBool("COMPARE_RESULTS", false)) {
    log << Stats::perfectMatchCount;
    log << Stats::positiveMismatchCount;
    log << Stats::negativeMismatchCount;
    log << Stats::positiveOptimalMismatchCount;
    log << Stats::negativeOptimalMismatchCount;
    log << Stats::positiveUpperBoundMismatchCount;
    log << Stats::negativeUpperBoundMismatchCount;
    log << Stats::positiveLowerBoundMismatchCount;
    log << Stats::negativeLowerBoundMismatchCount;
    log << "--------------------------------------------------------\n";
  }

  #ifdef IS_DEBUG_INFSBLTY_TESTS
    log << Stats::feasibilityTests;
    log << Stats::feasibilityHits;
    log << Stats::nodeSuperiorityInfeasibilityHits;
    log << Stats::rangeTighteningInfeasibilityHits;
    log << Stats::historyDominationInfeasibilityHits;
    log << Stats::relaxedSchedulingInfeasibilityHits;
    log << Stats::slotCountInfeasibilityHits;
    log << Stats::forwardLBInfeasibilityHits;
    log << Stats::backwardLBInfeasibilityHits;
    log << "--------------------------------------------------------\n";
  #endif

  #ifdef IS_DEBUG_SPD
    log << Stats::signatureDominationTests;
    log << Stats::signatureMatches;
    log << Stats::signatureAliases;
    log << Stats::subsetMatches;
    log << Stats::invalidDominationHits;
    log << Stats::absoluteDominationHits;
    log << Stats::positiveDominationHits;
    log << Stats::negativeDominationHits;
    log << Stats::dominationPruningHits;
    log << "----------------------------------------------------------\n";
  #endif

  #ifdef IS_DEBUG_BOUNDS
    log << Stats::totalInstructions;
    log << Stats::instructionsWithTighterFileLB;
    log << Stats::cyclesTightenedForTighterFileLB;
    log << Stats::instructionsWithTighterRelaxedLB;
    log << Stats::cyclesTightenedForTighterRelaxedLB;
    log << Stats::instructionsWithEqualLB;
    log << Stats::instructionsWithTighterFileUB;
    log << Stats::cyclesTightenedForTighterFileUB;
    log << Stats::instructionsWithTighterRelaxedUB;
    log << Stats::cyclesTightenedForTighterRelaxedUB;
    log << Stats::instructionsWithEqualUB;
    log << "----------------------------------------------------------\n";
  #endif

  #ifdef IS_DEBUG_READY_LIST
    log << Stats::maxReadyListSize;
    log << "----------------------------------------------------------\n";
  #endif

  log << "Statistics:\n";
  log << "-----------\n";

  log << Stats::problemSize;
  log << Stats::solvedProblemSize;

  #ifdef IS_DEBUG_ENUM
    log << Stats::nodeCount;
    log << Stats::nodesPerLength;
    log << Stats::solutionTime;
    log << Stats::solutionTimeForSolvedProblems;
    log << Stats::iterations;
  #endif

  log << "\n";
  #ifdef IS_DEBUG_SPD
    log << Stats::historyEntriesPerIteration;
    log << Stats::historyListSize;
    log << Stats::traversedHistoryListSize;
    log << Stats::historyDominationPosition;
    log << Stats::historyDominationPositionToListSize;
    log << Stats::historyTableInitializationTime;
  #endif
  log << "----------------------------------------------------------\n";

  if (config.GetBool("PRINT_TIMEOUTS", false)) log << Stats::timeouts;

  #ifdef IS_DEBUG_LATENCIES
    log << Stats::instructionTypeCounts;
    log << Stats::instructionTypeLatencies;
    log << Stats::dependenceTypeLatencies;
  #endif
}

void ProcessBlock(DataDepGraph* dataDepGraph) {
  char skipReason[1000];
  bool skip = false;

  crntBlockNum_++;

  if (dataDepGraph->GetInstCnt() > config.GetInt("MAX_DAG_SIZE")) {
    strncpy(skipReason, "exceeding max. size", 1000);
    skip = true;
  }

  if (dataDepGraph->GetWeight() < config.GetFloat("MIN_DAG_WEIGHT")) {
    strncpy(skipReason, "not meeting min. weight", 1000);
    skip = true;
  }

  if (dataDepGraph->IncludesUnpipelined()) {
    unpipelinedBlockCnt_++;
    //strncpy(skipReason, "including an unpipelined instruction", 1000);
    //skip = true;
  }

  if (dataDepGraph->IncludesUnsupported()) {
    unsupportedBlockCnt_++;
    strncpy(skipReason, "including an unsupported instruction", 1000);
    skip = true;
  }

  if (dataDepGraph->IncludesNonStandardBlock()) {
    nonStandardBlockCnt_++;
    strncpy(skipReason, "including a non-standard block", 1000);
    skip = true;
  }

  if (ShouldBeSkipped(crntBlockNum_)) {
    strncpy(skipReason, "being in skip list", 1000);
    skip = true;
  }

  if (skip) {
    Logger::Info("Skipped DAG #%lld with size %d and weight %f for %s.",
                 crntBlockNum_,
                 dataDepGraph->GetInstCnt(),
                 dataDepGraph->GetWeight(),
                 skipReason);
    retVal_ = -1;
  } else {
/*    Logger::Info("Processing DAG #%lld with ID %s and %d insts.",
                 crntBlockNum_,
                 dataDepGraph->GetDagID(),
                 dataDepGraph->GetInstCnt());*/
    ProcessBasicBlockWithSpillCost_(dataDepGraph);
  }
}

void ProcessBasicBlockWithSpillCost_(DataDepGraph* dataDepGraph) {
  Pruning prune;
  prune.rlxd = config.GetBool("APPLY_RELAXED_PRUNING", true);
  prune.nodeSup = config.GetBool("APPLY_NODE_SUPERIORITY", true);
  prune.histDom = config.GetBool("APPLY_HISTORY_DOMINATION", true);
  BBWithSpill* bb = new BBWithSpill(machMdl_,
                                    dataDepGraph,
                                    (long)crntBlockNum_,
                                    (int16_t)config.GetInt("HIST_TABLE_HASH_BITS"),
                                    ParseLBAlg(config.GetString("LB_ALG", "LC")),
                                    ParseHeuristic(config.GetString("HEURISTIC")),
                                    ParseHeuristic(config.GetString("ENUM_HEURISTIC")),
                                    config.GetBool("VERIFY_SCHEDULE", false),
                                    prune,
                                    config.GetBool("ENUMERATE_STALLS", true),
                                    (int)config.GetInt("SPILL_COST_FACTOR"));
  if (bb == NULL) Logger::Fatal("Out of memory.");
  ProcessRegion_(bb);
  delete bb;
}

FUNC_RESULT ProcessRegion_(SchedRegion* rgn) {
  bool isEasy;
  FUNC_RESULT rslt;

  Milliseconds start = Utilities::GetProcessorTime();
  rslt = rgn->BuildFromFile();
  Milliseconds buildTime = Utilities::GetProcessorTime() - start;
  Stats::regionBuildTime.Record(buildTime);

  if (rslt != RES_SUCCESS) return rslt;

  float weight = rgn->GetDepGraph()->GetWeight();

  InstCount normBestCost = 0;
  InstCount bestSchedLngth = 0;
  InstCount normHurstcCost = 0;
  InstCount hurstcSchedLngth = 0;

  InstCount costImprvmnt = 0;
  InstCount cycleImprvmnt = 0;

  InstCount costLwrBound = 0;
  InstCount hurstcCost = 0;
  InstCount bestMainSchedLngth = 0;
  InstCount hurstcMainSchedLngth = 0;

  InstSchedule* dummyScheduleOut;
  rslt = rgn->FindOptimalSchedule(config.GetBool("USE_FILE_BOUNDS", true),
                                  config.GetInt("TIMEOUT", INVALID_VALUE),
                                  isEasy,
                                  normBestCost,
                                  bestSchedLngth,
                                  bestMainSchedLngth,
                                  normHurstcCost,
                                  hurstcSchedLngth,
                                  hurstcMainSchedLngth,
                                  dummyScheduleOut);

  costImprvmnt = normHurstcCost - normBestCost;
  cycleImprvmnt = hurstcSchedLngth - bestSchedLngth;
  assert(config.GetInt("SPILL_COST_FACTOR") > 0 ||
         costImprvmnt == cycleImprvmnt);

  if (outFile_ != NULL) {
    rgn->GetDepGraph()->WriteToFile(
        outFile_, rslt, costImprvmnt, (long)crntBlockNum_);
  }

  if (rslt != RES_ERROR) lstSchduldBlockCnt_++;
  if (rslt == RES_ERROR || rslt == RES_FAIL) return rslt;

  costLwrBound = rgn->GetCostLwrBound();
  hurstcCost = normHurstcCost + costLwrBound;

  totLstSchedLngth_ += hurstcSchedLngth;
  wghtdTotLstSchedLngth_ += ((float)hurstcSchedLngth * weight);
  wghtdTotHurstcCost_ += ((float)hurstcCost * weight);

  Stats::scheduledLatency.Record(rgn->GetDepGraph()->GetMaxLtncy());

  if (isEasy == false) {
    totHardSchedLngth_ += hurstcSchedLngth;
    wghtdTotHardSchedLngth_ += ((float)hurstcSchedLngth * weight);
    wghtdTotHardCost_ += ((float)hurstcCost * weight);
  }

  if (isEasy) easyBlockCnt_++;

  if (costImprvmnt > 0 || cycleImprvmnt > 0) {
    RcrdImprvmnt(costImprvmnt, cycleImprvmnt, dataDepGraph_, rslt == RES_SUCCESS);
  }

  if (rslt == RES_SUCCESS) {
    assert(costImprvmnt >= 0);
    assert(!(isEasy && costImprvmnt > 0));
  }

  if (rslt == RES_TIMEOUT) {
    timedOutBlockCnt_++;
    if (config.GetBool("PRINT_TIMEOUTS", false)) {
      InstCount lb, ub;
      dataDepGraph_->GetFinalBounds(lb, ub);
      InstCount instCount = dataDepGraph_->GetInstCnt();
      Stats::timeouts.Record((int)crntBlockNum_, instCount, lb, ub);
    }
  }

  schduldBlockCnt_++;

  if (config.GetBool("COMPARE_RESULTS", false)) {
    CmprRslts_(dataDepGraph_, rslt);
  }

//  Logger::Info("------------------------------------------------");

  return rslt;
}

void RcrdImprvmnt(int costImprvmnt, int cycleImprvmnt,
                            DataDepGraph* depGraph, bool isOptimal) {
  float weight = depGraph->GetWeight();
  float wghtdCostImprvmnt = costImprvmnt * weight;
  float wghtdCycleImprvmnt = cycleImprvmnt * weight;

  if (isOptimal) {
    optmlImprvdBlockCnt_++;
  } else {
    nonOptmlImprvdBlockCnt_++;
  }

  cycleImprvmnt_ += cycleImprvmnt;
  wghtdCycleImprvmnt_ += wghtdCycleImprvmnt;
  wghtdCostImprvmnt_ += wghtdCostImprvmnt;

  if (costImprvmnt > maxImprvmnt_) {
    maxImprvmnt_ = costImprvmnt;
    maxImprvdDagId_ = depGraph->GetDagID();
  }

  if (wghtdCostImprvmnt > maxWghtdImprvmnt_) {
    maxWghtdImprvmnt_ = wghtdCostImprvmnt;
    maxWghtImprvdDagId_ = depGraph->GetDagID();
  }

  retVal_ = 0;
}

void CmprRslts_(DataDepGraph* dataDepGraph, FUNC_RESULT rslt) {
  InstCount fileLwrBound, fileUprBound;
  InstCount finalLwrBound, finalUprBound;
  bool isMatch = true;

  dataDepGraph->GetFileSchedBounds(fileLwrBound, fileUprBound);
  assert(fileLwrBound <= fileUprBound);
  dataDepGraph->GetFinalBounds(finalLwrBound, finalUprBound);
  assert(finalLwrBound <= finalUprBound);

  bool isFileOptml = fileLwrBound == fileUprBound;
  bool isThisOptml = finalLwrBound == finalUprBound;

  assert(isThisOptml || rslt == RES_TIMEOUT || rslt == RES_ERROR);

  if (isFileOptml && isThisOptml) {
    isMatch = (finalLwrBound == fileLwrBound &&
               finalUprBound == fileUprBound);

    if (isMatch) {
      Stats::perfectMatchCount++;
    } else {
      if (finalUprBound < fileUprBound) Stats::positiveMismatchCount++;
      if (finalUprBound > fileUprBound) {
        Stats::negativeMismatchCount++;
        #ifdef IS_DEBUG
          Logger::Info("Problematic graph encountered.");
        #endif
      }

      Logger::Error("Mismatch in DAG %lld: Range=[%d, %d] File Range=[%d, %d].",
                    crntBlockNum_, finalLwrBound, finalUprBound,
                    fileLwrBound, fileUprBound);
    }
  } else {
    if (finalUprBound < fileUprBound) Stats::positiveUpperBoundMismatchCount++;
    if (finalUprBound > fileUprBound) Stats::negativeUpperBoundMismatchCount++;
    if (finalLwrBound > fileLwrBound) Stats::positiveLowerBoundMismatchCount++;
    if (finalLwrBound < fileLwrBound) Stats::negativeLowerBoundMismatchCount++;
  }

  if (isThisOptml && !isFileOptml) Stats::positiveOptimalMismatchCount++;
  if (!isThisOptml && isFileOptml) Stats::negativeOptimalMismatchCount++;
}

bool ShouldBeSkipped(int64_t blockNum) {
  if (skipMin_ != INVALID_VALUE) {
    if (blockNum >= skipMin_ && blockNum <= skipMax_) return true;
  }

  if (config.GetBool("PROCESS_SOLVED_ONLY", false)) {
    InstCount fileLwrBound, fileUprBound;
    dataDepGraph_->GetFileSchedBounds(fileLwrBound, fileUprBound);
    assert(fileLwrBound <= fileUprBound);
    if (fileLwrBound != fileUprBound) return true;
  }

  return skipSet_.find(blockNum) != skipSet_.end();
}

LISTSCHED_HEURISTIC ParseHeuristic(const string& name) {
  if (name == "CP") {
    return LSH_CP;
  } else if (name == "FSO") {
    return LSH_FSO;
  } else {
    Logger::Error("Unrecognized heuristic. Defaulted to CP.");
    return LSH_CP;
  }
}

LB_ALG ParseLBAlg(const string& name) {
  if (name == "RJ") {
    return LBA_RJ;
  } else if (name == "LC") {
    return LBA_LC;
  } else {
    Logger::Error("Unrecognized lower bound technique. Defaulted to Rim-Jain");
    return LBA_RJ;
  }
}

OUTPUT_DAGS ParseOutptDagsFilter(const string& name) {
  if (name == "ALL") {
    return ODG_ALL;
  } else if (name == "IMP") {
    return ODG_IMP;
  } else if (name == "OPT") {
    return ODG_OPT;
  } else if (name == "HARD") {
    return ODG_HARD;
  } else if (name == "NONE") {
    return ODG_NONE;
  } else {
    Logger::Error("Invalid output Dags option. Defaulted to NONE.");
    return ODG_NONE;
  }
}
